package org.jflame.uid.worker;

import org.jflame.uid.IDGenerateException;
import org.jflame.uid.snowflake.utils.DockerUtils;
import org.jflame.uid.utils.NetUtils;
import org.jflame.uid.utils.Util;
import org.jflame.uid.worker.dao.IWorkerNodeDao;
import org.jflame.uid.worker.entity.WorkerNodeEntity;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 可重用的WorkerIdAssigner实现类.
 * <p>
 * host=ip,port=应用的classpath根目录hashcode.使用相同值登记过一次后下次仍使用旧的workerid,workerid缓存在本地文件一份<br>
 * 节点数据持久化基于db,redis或zookeeper
 * 
 * @author charles.zhang
 */
public class ReusableWorkerIdAssigner implements WorkerIdAssigner {

    private final Logger logger = LoggerFactory.getLogger(ReusableWorkerIdAssigner.class);
    private String cacheIdFile = "workerid.cache";
    private IWorkerNodeDao workerNodeDao;

    public ReusableWorkerIdAssigner(IWorkerNodeDao workerNodeDao) {
        this.workerNodeDao = workerNodeDao;
    }

    @Override
    public int assignWorkerId() {
        // 1.获取host/port对应的已注册过的workernode
        WorkerNodeEntity node = buildWorkerNode();
        if (logger.isDebugEnabled()) {
            logger.debug("开始获取workid:{}", node);
        }
        WorkerNodeEntity oldNode = workerNodeDao.getWorkerNodeByHostPort(node.getHostName(), node.getPort());
        if (oldNode != null) {
            return oldNode.getId();
        }
        // 2.不存在则新注册一个
        boolean success = workerNodeDao.registerWorkerNode(node);
        if (success) {
            cacheWorkerId(node.getId());
            logger.info("新注册workerId:{}", node.getId());
        } else {
            int wid = getCacheWorkerId();
            if (wid > 0) {
                logger.info("注册workerId失败,使用本地缓存ID");
                node.setId(wid);
            }
        }
        if (node.getId() == 0) {
            throw new IDGenerateException("注册workId失败");
        }
        return node.getId();
    }

    /**
     * 获取已注册过的workerid
     * 
     * @param node
     * @return
     */
    // protected abstract Integer getWorkerId(WorkerNodeEntity node);

    /**
     * 注册新的workerNode
     * 
     * @param node
     * @return
     */
    // protected abstract boolean registerWokerNode(WorkerNodeEntity node);

    protected WorkerNodeEntity buildWorkerNode() {
        WorkerNodeEntity workerNodeEntity = new WorkerNodeEntity();
        if (DockerUtils.isDocker()) {
            workerNodeEntity.setNodeType(WorkerNodeType.CONTAINER.value());
            workerNodeEntity.setHostName(DockerUtils.getDockerHost());
            workerNodeEntity.setPort(DockerUtils.getDockerPort());
        } else {
            workerNodeEntity.setNodeType(WorkerNodeType.ACTUAL.value());
            workerNodeEntity.setHostName(NetUtils.getLocalAddress());
            // 用classpath根目录URL.hashcode作为区分(因为有些程序并不需要开启端口所以不使用端口号区分)
            workerNodeEntity.setPort(String.valueOf(Util.getDefaultClassLoader()
                    .getResource("")
                    .hashCode()));
        }

        return workerNodeEntity;
    }

    /**
     * 获取本地缓存的id.在注册中心连接失败时使用
     * 
     * @return
     * @throws URISyntaxException
     */
    int getCacheWorkerId() {
        try {
            URL url = Util.getDefaultClassLoader()
                    .getResource(cacheIdFile);
            if (url != null) {
                Path path = Paths.get(url.toURI());
                List<String> txts = Files.readAllLines(path, StandardCharsets.UTF_8);
                if (txts != null && !txts.isEmpty()) {
                    return Integer.parseInt(txts.get(0)
                            .trim());
                }
            }
        } catch (URISyntaxException | IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    void cacheWorkerId(int id) {
        try {
            Path classpath = Paths.get(Util.getDefaultClassLoader()
                    .getResource("")
                    .toURI());
            Path cacheFilePath = classpath.resolve(cacheIdFile);
            if (!Files.exists(cacheFilePath)) {
                Files.createFile(cacheFilePath);
            }
            Files.write(cacheFilePath, String.valueOf(id)
                    .getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
        } catch (URISyntaxException | IOException e) {
            e.printStackTrace();
        }
    }

    /* public void setWorkerNodeDao(IWorkerNodeDao workerNodeDao) {
        this.workerNodeDao = workerNodeDao;
    }*/

}
